/*
 * Copyright (c) 2009 VMware, Inc.
 * Copyright (c) 2009 John Pritchard, WTKX Project Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wtkx.ui;

import java.awt.Color;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Application threading ({@link java.util.Timer Task}) utilities.
 *
 * @author gbrown
 * @author jdp
 */
public final class Threads 
    extends Object
{

    /**
     * Schedules a task for repeated execution. The task will be executed on the
     * UI thread.
     *
     * @param runnable The task to execute.
     * @param period The interval at which the task should be executed.
     *
     * @return An integer Task ID that can be used to cancel execution
     * of the task, in milliseconds.
     */
    public static int SetInterval(Runnable runnable, long period) {
        int intervalID = nextTimerTaskID++;

        IntervalTask intervalTask = new IntervalTask(runnable);
        TimerTaskMap.put(intervalID, intervalTask);

        try {
            timer.schedule(intervalTask, 0, period);
        }
        catch (IllegalStateException exception) {
            /*
             * Dead timer thread
             */
            timer = NewTimer();
            TimerTaskMap.clear();
            TimerTaskMap.put(intervalID, intervalTask);
            timer.schedule(intervalTask, 0, period);
        }

        return intervalID;
    }

    /**
     * Schedules a task for execution after an elapsed time.
     *
     * @param runnable The task to execute.
     * @param timeout The time after which the task should begin
     * executing, in milliseconds.
     */
    public static int SetTimeout(Runnable runnable, long timeout) {
        int timeoutID = nextTimerTaskID++;

        TimerTimeoutTask timeoutTask = new TimerTimeoutTask(runnable, timeoutID);
        TimerTaskMap.put(timeoutID, timeoutTask);

        try {
            timer.schedule(timeoutTask, timeout);
        }
        catch (IllegalStateException exception) {
            /*
             * Dead timer thread
             */
            timer = NewTimer();
            TimerTaskMap.clear();
            nextTimerTaskID = 1;
            TimerTaskMap.put(timeoutID, timeoutTask);
            timer.schedule(timeoutTask, timeout);
        }

        return timeoutID;
    }

    /**
     * Cancels execution of a timer task.
     *
     * @param timerTaskID Task identifier
     */
    public static void ClearTimeout(int timerTaskID) {
        TimerTask timerTask = TimerTaskMap.remove(timerTaskID);
        if (timerTask != null) {
            timerTask.cancel();
        }
    }

    /**
     * Cancels execution of a scheduled task.
     *
     * @param intervalID The ID of the task to cancel.
     */
    public static void ClearInterval(int intervalID) {
        ClearTimeout(intervalID);
    }

    /**
     * Schedules a task for execution via the AWT Event Queue after an
     * elapsed time.
     *
     * @param runnable The task to execute.
     * @param timeout The time after which the task should begin
     * executing, in milliseconds.
     */
    public static int SetQueueTimeout(Runnable runnable, long timeout) {
        int timeoutID = nextTimerTaskID++;

        QueueTimeoutTask timeoutTask = new QueueTimeoutTask(runnable, timeoutID);
        TimerTaskMap.put(timeoutID, timeoutTask);

        try {
            timer.schedule(timeoutTask, timeout);
        }
        catch (IllegalStateException exception) {
            /*
             * Dead timer thread
             */
            timer = NewTimer();
            TimerTaskMap.clear();
            nextTimerTaskID = 1;
            TimerTaskMap.put(timeoutID, timeoutTask);
            timer.schedule(timeoutTask, timeout);
        }

        return timeoutID;
    }

    /**
     * Cancels execution of a scheduled task.
     *
     * @param timeoutID The ID of the task to cancel.
     */
    public static void ClearQueueTimeout(int timeoutID) {
        ClearTimeout(timeoutID);
    }

    /**
     * A task to execute from the AWT Event Queue.  Returns without
     * waiting for the task to complete.
     *
     * @param runnable A task to execute from the AWT Event Queue.
     */
    public static void QueueCallback(Runnable runnable) {
        QueueCallback(runnable, false);
    }

    /**
     * A task to execute from the AWT Event Queue.  Optionally waits
     * for the task to complete.
     *
     * @param runnable A task to execute from the AWT Event Queue.  
     * @param wait If <tt>true</tt>, does not return until the
     * runnable has executed.  Otherwise, returns immediately.
     */
    public static void QueueCallback(Runnable runnable, boolean wait) {
        if (wait) {
            try {
                java.awt.EventQueue.invokeAndWait(runnable);
            }
            catch (InvocationTargetException exception) {
            }
            catch (InterruptedException exception) {
            }
        }
        else {
            try {
                java.awt.EventQueue.invokeLater(runnable);
            }
            catch (IllegalThreadStateException exc){
                /*
                 * dead TG? 
                 * 
                 * See java/awt/EventQueue#threadGroup 
                 * 
                 * does it get it right the second time?
                 */
                java.awt.EventQueue.invokeLater(runnable);
            }
        }
    }

    /**
     * Opens the resource at the given location.
     *
     * @param location
     */
    public static void Open(URL location) {
        try {
            Class<?> desktopClass = Class.forName("java.awt.Desktop");
            Method getDesktopMethod = desktopClass.getMethod("getDesktop", new Class<?>[] {});
            Method browseMethod = desktopClass.getMethod("browse", new Class[] {URI.class});
            Object desktop = getDesktopMethod.invoke(null, (Object[]) null);
            browseMethod.invoke(desktop, location.toURI());
        } catch (Exception exception) {
            System.out.println("Unable to open URL in default browser.");
        }
    }

    /**
     * Issues a system alert sound.
     */
    public static void Beep() {
        java.awt.Toolkit.getDefaultToolkit().beep();
    }

    /**
     * Daemon thread group for internal threads.
     * @see wtkx.in.Animator
     */
    public final static class Group
        extends java.lang.ThreadGroup
    {
        Group(){
            super("WTKX");
            this.setDaemon(true);
        }
    }
    private static Group Instance;
    public final static synchronized Group Group(){
        if (null == Instance || Instance.isDestroyed())
            Instance = new Group();
        return Instance;
    }

    public static class IntervalTask
        extends TimerTask
    {
        private final Runnable runnable;

        public IntervalTask(Runnable runnable) {
            super();
            this.runnable = runnable;
        }

        public void run() {
            QueueCallback(runnable);
        }
    }

    public static class TimerTimeoutTask
        extends TimerTask
    {
        private final Runnable runnable;

        private final int timeoutID ;

        public TimerTimeoutTask(Runnable runnable, int timeoutID) {
            super();
            this.runnable = runnable;
            this.timeoutID = timeoutID;
        }

        public void run() {
            try {
                this.runnable.run();
            }
            finally {
                ClearTimeout(timeoutID);
            }
        }
    }

    public static class QueueTimeoutTask
        extends TimerTask
    {
        private final Runnable runnable;

        private final int timeoutID ;

        public QueueTimeoutTask(Runnable runnable, int timeoutID) {
            super();
            this.runnable = runnable;
            this.timeoutID = timeoutID;
        }

        public void run() {
            try {
                QueueCallback(runnable, true);
            }
            finally {
                ClearQueueTimeout(timeoutID);
            }
        }
    }

    /*
     * Not final in case of dead timer threads.
     */
    private volatile static int TimerCounter = 1;
    private final static Timer NewTimer(){
        return (new Timer("Wtkx-Timer-"+String.valueOf(TimerCounter++),true));
    }
    private static Timer timer = NewTimer();
    private final static Hashtable<Integer, TimerTask> TimerTaskMap = new Hashtable<Integer, TimerTask>();
    private volatile static int nextTimerTaskID = 0;

    public final static void Shutdown(){
        for (Integer tid : TimerTaskMap.keySet()){
            ClearTimeout(tid);
        }
    }

    /*
     * Not instantiated
     */
    private Threads(){
        super();
    }
}
